Launch Modes
Every Activity lives inside a Task. How it gets there — and whether it shares space with others — is controlled by launch modes and Intent flags. Mastering this is the key to predictable navigation.
What is a Task?
A Task is a collection of activities that users interact with as a unit. It defines a "workflow" — a cohesive experience that starts when the user launches an app and ends when they press Back all the way out. Android maintains tasks separately from your app's process.
Think of a Task as a stack of screens. The user always interacts with the screen at the top. When they press Back, the top screen is popped and the one beneath it becomes active. This is the Android back stack.
Tasks vs processes
These are completely independent concepts that developers often confuse:
When the system kills your process to reclaim memory, the Task record survives. When the user taps your app icon again, Android recreates the Activities from the task's record, restoring the back stack using the saved instance state bundles.
The Recents screen
The Recents screen (Overview / App Switcher) shows one card per Task, not per app. An app can have multiple tasks — each gets its own card. This is why opening a document link from Gmail might show two Gmail cards in Recents: one for the main inbox task and one for the document viewer task.
The Back Stack
The back stack is a LIFO (Last In, First Out) stack of Activity instances within a Task. Every time you call startActivity(), a new Activity is pushed onto the stack. Every time the user presses Back (or calls finish()), the top Activity is popped and destroyed.
What happens on Back press
When the user presses Back on the last Activity in a Task (the root Activity), the Task is moved to the background. The process may keep running, but the task is no longer visible. If the user returns via Recents or the launcher icon, the task resumes exactly where they left it — as long as the system hasn't killed the process.
onBackPressed() to call finish() manually — that's what the system already does. Only override it when you need custom behavior like "confirm before exit" dialogs or custom transition handling.Multiple tasks in the background
Android keeps multiple tasks in memory simultaneously. When you switch apps (via Recents or the launcher), Android brings the selected app's task to the foreground. The previously visible task moves to the background — its Activities go through onPause → onStop but are not destroyed. They're simply hidden, waiting.
Task Affinity
Every Activity has a task affinity — a string identifier for which task it "prefers" to belong to. By default, all Activities in an app share the same affinity: the app's package name. This means they naturally live in the same task.
Task affinity only matters in two specific situations: when an Activity is launched with FLAG_ACTIVITY_NEW_TASK, or when an Activity has allowTaskReparenting="true".
<!-- Default: affinity = package name --> <activity android:name=".MainActivity" /> <!-- Custom affinity — prefers a different task --> <activity android:name=".DocumentActivity" android:taskAffinity="com.myapp.documents" android:launchMode="singleTask" /> <!-- No affinity — never shares a task --> <activity android:name=".IsolatedActivity" android:taskAffinity="" />
allowTaskReparenting
When set to true, an Activity can move between tasks. If Activity C (with affinity "com.other") was started by your app and placed in your task, the next time "com.other" is brought to the foreground, Activity C will reparent — it migrates from your task to the "com.other" task automatically. This is how Android handles email links to other apps.
standard
The default. Every call to startActivity() creates a brand new instance of the Activity and pushes it onto the current task's back stack — regardless of whether an instance already exists anywhere in the stack.
Calling startActivity(A) when A is already at the top creates a second instance of A on the stack. The user must press Back twice to dismiss both.
This can lead to the infamous "double-A" stack if a notification or deep link always starts from the Activity rather than checking existing state.
The vast majority of Activities. Any screen that can logically exist multiple times in a navigation flow — detail screens, edit forms, sub-pages.
If you're using Jetpack Navigation Component, you rarely deal with launch modes directly — NavController handles this correctly by default.
singleTop
If an instance of the Activity is already at the top of the back stack, Android reuses it instead of creating a new one. It calls onNewIntent(intent) on the existing instance. If the Activity is not at the top (even if it exists elsewhere in the stack), a new instance is created normally.
setIntent(intent) to update getIntent(), then process the new data. The Activity does NOT go through onCreate again.Scenario 1: Stack is A→B (B at top). Start B again → onNewIntent(B) called on existing B. Stack stays A→B.
Scenario 2: Stack is A→B→C. Start B → new B² created. Stack becomes A→B→C→B².
Push notifications that deep-link to an Activity — prevents stacking the same screen repeatedly when the user taps multiple notifications.
Search results where search fires a new intent on the same screen. Each search updates the displayed results without creating nested screens.
// AndroidManifest.xml // android:launchMode="singleTop" class NotificationActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) handleIntent(intent) // handle initial launch } override fun onNewIntent(intent: Intent) { super.onNewIntent(intent) setIntent(intent) // update getIntent() for future calls handleIntent(intent) // same handler — no duplicated logic } private fun handleIntent(intent: Intent) { val notifId = intent.getStringExtra("notif_id") ?: return viewModel.loadNotification(notifId) } }
singleTask
The most powerful and most misunderstood launch mode. The system ensures only one instance exists across the entire task. When the Activity is launched:
If an instance already exists anywhere in its task → the system brings that task to the foreground, clears everything above it in the stack, and calls onNewIntent(). If no instance exists → creates a new one (potentially in a new task if the affinity differs).
Stack: Home→List→Detail→Edit. Launch Home (singleTask) → Edit and Detail are destroyed. Stack becomes: Home. onNewIntent fires on Home.
This makes singleTask perfect for the root/home Activity — it's exactly the behavior users expect when tapping the app's home icon.
Home/root Activity — the one the app launcher icon opens. Tapping home should always bring you to the start of your app's flow.
Login Activity — once logged in, navigating to login from deep inside should clear the authenticated stack.
Browser-like pivots — where re-entering a section should reset its internal stack.
singleInstance
The most isolated mode. The Activity gets its own private task — no other Activity can ever be added to that task. It is the only Activity in its task, always. Any Activity launched from a singleInstance Activity goes into a different task.
When re-launched: the existing instance is reused and onNewIntent() is called, exactly like singleTask. But unlike singleTask, there's nothing above it to clear — it's always alone.
If your app's Activity A launches singleInstance Activity S, and S then launches B — B goes into a new task (your app's default task), not into S's private task.
Pressing Back from S takes the user to whatever task was in the foreground before, not necessarily to A.
System-level screens that must be globally unique across the device — like the Android Dialer during a call, a system VPN screen, or a phone launcher.
Shared functionality between apps — if your app exposes an Activity that other apps can reuse and should always be a single instance.
In practice, very few production apps need singleInstance.
Intent Flags
Intent flags give you per-launch control over how an Activity is placed on the back stack — without changing the manifest. They override or augment the declared launch mode for a single call. They're especially important for notification deep links, shortcuts, and cross-app launches.
Start the Activity in a new task. If a task with the Activity's affinity already exists, that task is brought to the foreground instead of creating a new one. Required when starting an Activity from a non-Activity context (Service, BroadcastReceiver, Application). Does NOT guarantee a new task if one with matching affinity exists.
// From a Service or BroadcastReceiver val intent = Intent(this, MainActivity::class.java).apply { flags = Intent.FLAG_ACTIVITY_NEW_TASK } startActivity(intent)
If the target Activity already exists in the task, destroy all Activities above it and bring it to the top. Combined with FLAG_ACTIVITY_SINGLE_TOP, it calls onNewIntent() on the existing instance instead of destroying and recreating it. This combination is the Intent-flag equivalent of singleTask.
Without SINGLE_TOP: the existing instance is destroyed and recreated (onCreate called). With SINGLE_TOP: the existing instance is reused and onNewIntent called.
// Navigate "home" clearing all screens above val intent = Intent(this, HomeActivity::class.java).apply { flags = Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_SINGLE_TOP } startActivity(intent)
The Intent-flag equivalent of launchMode="singleTop". If the target Activity is already at the top of the stack, reuse it (call onNewIntent) instead of creating a new instance. Only applies to the current call — does not change the manifest declaration permanently.
The Activity will not be kept in the history stack. As soon as the user moves away from it, it is immediately finished. Useful for login/auth screens you never want the user to return to via Back, or splash screens.
Clears the entire task before launching the Activity. Must be used with FLAG_ACTIVITY_NEW_TASK. All Activities in the target task are destroyed. Use for logout flows — after logout you want the user to start fresh in the login screen with nothing to go Back to.
fun logout() { authManager.signOut() val intent = Intent(this, LoginActivity::class.java).apply { flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK } startActivity(intent) // all activities destroyed, LoginActivity fresh }
If the Activity already exists anywhere in the task, move it to the front without destroying anything. Unlike CLEAR_TOP, the Activities that were above it are not destroyed — they just move behind it. The back stack order is reshuffled. Useful for tab-like navigation where you want to preserve all screens.
The Task will not appear in the Recents (Overview) screen. Useful for authentication flows, OTP entry screens, or any transient Activity that the user should not be able to return to from the app switcher. Can also be set in the manifest with android:excludeFromRecents="true".
Launch Mode Playground
Select a launch mode, then tap activities to push them onto the stack. See exactly how each mode affects the back stack in real time.
Real-world Scenarios
Knowing the theory is one thing. Here's how launch modes and flags map to actual product requirements:
Symptom: User taps 3 notifications. Presses Back 3 times before reaching the previous screen.
Fix: Use launchMode="singleTop" on the notification target Activity, and set FLAG_ACTIVITY_SINGLE_TOP in the PendingIntent. Each notification updates the existing instance via onNewIntent instead of creating a new one.
Better fix: Use TaskStackBuilder to construct a synthetic back stack, combined with singleTop on the target.
Symptom: User logs out, lands on LoginActivity, presses Back and sees the previous user's data.
Fix: FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_CLEAR_TASK when launching LoginActivity on logout. This destroys the entire task history — the Back button on Login exits the app.
intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
Symptom: User taps a link in Chrome, gets to ProductDetailActivity. Presses Back — goes back to Chrome instead of your app's home screen.
Fix: Use TaskStackBuilder with addParentStack() in the PendingIntent. This builds a synthetic back stack (Home → Category → Product) so Back navigates within your app.
val stackBuilder = TaskStackBuilder.create(this) stackBuilder.addParentStack(ProductDetailActivity::class.java) stackBuilder.addNextIntent(Intent(this, ProductDetailActivity::class.java).apply { putExtra("product_id", productId) }) val pendingIntent = stackBuilder.getPendingIntent(0, PendingIntent.FLAG_UPDATE_CURRENT)
Symptom: User on Home tab, navigates to Settings, taps Home tab again — gets a duplicate Home on the stack instead of returning to the original.
Fix: Use FLAG_ACTIVITY_REORDER_TO_FRONT for tab switches to bring existing instances forward, or better — use Jetpack Navigation with a NavGraph that handles this automatically via popUpTo and launchSingleTop=true in the navigation XML.
Symptom: App is open on screen A. User receives a notification, taps it, expects to land on screen B (notification target). Instead they land on a separate task with just screen B and no app history.
Fix: Check whether your app's task is already running before constructing the PendingIntent. Use FLAG_ACTIVITY_CLEAR_TOP | FLAG_ACTIVITY_SINGLE_TOP if you want to navigate within the existing task, or build a proper stack with TaskStackBuilder.